1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Parameter related functions and types
//!
//! # Functions
//!
//! * [`parameters`](VoicemeeterRemote::parameters)
//! * [`is_parameters_dirty`](VoicemeeterRemote::is_parameters_dirty)
//! * [`get_parameter_float`](VoicemeeterRemote::get_parameter_float)
//! * [`get_parameter_string`](VoicemeeterRemote::get_parameter_string)
use std::{
    ffi::{CStr, CString, NulError},
    os::raw::c_char,
    ptr,
};

use crate::types::ParameterNameRef;

use crate::VoicemeeterRemote;

impl VoicemeeterRemote {
    // TODO: Only call from one thread, limit it
    /// Check if parameters have changed
    ///
    /// Call this function periodically to check if parameters have changed, typically every 10ms.
    /// This function will also make sure voicemeeter processes any new pushed values for a parameter.
    ///
    /// # Security
    ///
    /// This method must only be called from one thread.
    pub fn is_parameters_dirty(&self) -> Result<bool, IsParametersDirtyError> {
        let res = unsafe { self.raw.VBVMR_IsParametersDirty() };
        match res {
            0 => Ok(false),
            1 => Ok(true),
            -1 => Err(IsParametersDirtyError::CannotGetClient),
            -2 => Err(IsParametersDirtyError::NoServer),
            s => Err(IsParametersDirtyError::Other(s)),
        }
    }

    /// Get the float value of a parameter. See also [`VoicemeeterRemote::parameters()`] to do this with functions.
    #[tracing::instrument(skip(self))]
    pub fn get_parameter_float(&self, param: &ParameterNameRef) -> Result<f32, GetParameterError> {
        let mut f = 0.0f32;
        let param = CString::new(param.as_ref())?;
        tracing::debug!("getting float parameter");
        let res = unsafe {
            self.raw
                .VBVMR_GetParameterFloat(param.as_ptr() as *mut _, &mut f)
        };
        match res {
            0 => Ok(f),
            -1 => Err(GetParameterError::CannotGetClient),
            -2 => Err(GetParameterError::NoServer),
            -3 => Err(GetParameterError::UnknownParameter(
                param.to_string_lossy().into_owned(),
            )), // NOTE: Lossless always (assuming vmr doesn't modify :) ), unsafe?
            -5 => Err(GetParameterError::StructureMismatch(
                param.to_string_lossy().into_owned(),
                "float",
            )),
            s => Err(GetParameterError::Other(s)),
        }
    }

    /// Get the string value of a parameter. See also [`VoicemeeterRemote::parameters()`] to do this with functions.
    #[tracing::instrument(skip(self))]
    pub fn get_parameter_string(
        &self,
        param: &ParameterNameRef,
    ) -> Result<String, GetParameterError> {
        let param = CString::new(param.as_ref()).unwrap();
        let mut output = [0 as c_char; 512];
        tracing::debug!("getting string parameter");
        let res = unsafe {
            self.raw
                .VBVMR_GetParameterStringA(param.as_ptr() as *mut _, ptr::addr_of_mut!(output[0]))
        };
        match res {
            0 => {
                let output = unsafe { CStr::from_ptr(ptr::addr_of!(output[0])) }
                    .to_string_lossy()
                    .into_owned();
                Ok(output)
            }
            -1 => Err(GetParameterError::CannotGetClient),
            -2 => Err(GetParameterError::NoServer),
            -3 => Err(GetParameterError::UnknownParameter(
                param.to_string_lossy().into_owned(),
            )), // NOTE: Lossless always (assuming vmr doesn't modify :) ), unsafe?
            -5 => Err(GetParameterError::StructureMismatch(
                param.to_string_lossy().into_owned(),
                "float",
            )),
            s => Err(GetParameterError::Other(s)),
        }
    }
}

/// Errors that can happen when getting a parameter.
#[derive(Debug, thiserror::Error, Clone)]
#[non_exhaustive]
pub enum GetParameterError {
    /// Could not make a c-compatible string. This is a bug.
    #[error("could not make into a c-string")]
    NulError(#[from] NulError),
    /// Unexpected error
    #[error("error (unexpected)")]
    CannotGetClient,
    /// No server.
    #[error("no server")]
    NoServer,
    /// Unknown parameter.
    #[error("unknown parameter: {0}")]
    UnknownParameter(String),
    /// Structure mismatch.
    #[error("tried to parse parameter {0:?} as a {1} but it is not")]
    StructureMismatch(String, &'static str),
    /// An unknown error code occured.
    #[error("unexpected error occurred: error code {0}")]
    Other(i32),
}

/// Errors that can happen when querying parameter "dirty" flag.
#[derive(Debug, thiserror::Error, Clone)]
#[non_exhaustive]
pub enum IsParametersDirtyError {
    /// Unexpected error
    #[error("error (unexpected)")]
    CannotGetClient,
    /// No server.
    #[error("no server")]
    NoServer,
    /// An unknown error code occured.
    #[error("unexpected error occurred: error code {0}")]
    Other(i32),
}